package com.xaioran.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;

@Configuration

@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    @Autowired
    private  MyUserDetailsService myUserDetailsService;

    @Autowired
    private MyPasswordEncoder myPasswordEncoder;

    //默认是的单实列，每次创建的时候都会去容器中找，找到就直接拿出来用。
    //可以通过  @Scope(value="")指定
    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    //对springSecrity 进行的设置
    protected void configure(HttpSecurity security) throws Exception {

        //PersistentTokenRepository的接口实现类-----》JdbcTokenRepositoryImpl
        JdbcTokenRepositoryImpl tokenRepository=new JdbcTokenRepositoryImpl();
        //引入数据源
        tokenRepository.setDataSource(dataSource);
        //配置createTableOnStartup为true是为了在数据库注解中创建我们的 persistent_logins表
        tokenRepository.setCreateTableOnStartup(true);
        //调这个方法建表
        tokenRepository.initDao();

        security
                .authorizeRequests()//请求授权
                .antMatchers("/index.jsp")//针对/index.jsp访问路径授权
                .permitAll()//可以无条件访问

                .antMatchers("/layui/**")//针对/layui下面的所有访问路径授权
                .permitAll()//可以无条件访问

                .antMatchers("/level1/**")//针对/level1下面的所有访问路径授权
                .hasRole("学徒")//设置访问 "/level1/**" 路径，用户必须要有 角色

                .antMatchers("/level2/**")//针对/level2下面的所有访问路径授权
                .hasAuthority("内门弟子")//设置访问 "/level2/**" 路径，用户必须要有 权限

                .and()
                .authorizeRequests() //对请求进行授权
                .anyRequest() //任意请求
                .authenticated() //需要登录才可以访问

                .and()
                .formLogin()  //使用表单的形式登录
                /*
                loginPage 的特殊说明：
                指定登录页的同时会影响到：“提交登录表单的地址” “退出登录地址” “登录失败地址”
                /index.jsp GET - 登录请求
                /index.jsp POST - 提交登录的表单
                /index.jsp?error GET - 重定向到此处以进行失败的身份验证尝试   （登陆失败）
                 /index.jsp?logout GET - 成功注销后重定向到这里 （退出登录）

                 通过LoginProcessingUrl()方法指定登录地址
                */
                .loginPage("/index.jsp") //指定登录页面（如果没有指定登录页面，会访问springSecurty自带的登录页）
                .loginProcessingUrl("/do/login.html") //指定去提交表单的地址
                .permitAll()//可以无条件访问 ,登录地址本身也需要去放行
                .usernameParameter("loginAcct") //定制登录账号的请求参数名
                .passwordParameter("userPswd") //定制登录密码的请求参数名字
                .defaultSuccessUrl("/main.html")//登录成功去哪
                //.and()
                //.csrf().disable() // 禁用CSRF 功能
                .and()
                .logout().logoutUrl("/do/logout.html")//指定退出地址
                .logoutSuccessUrl("/index.jsp")//退出成功去哪
                .and()
                .exceptionHandling() //指定异常处理
                //.accessDeniedPage("/to/no/auth/page.html") //访问被拒绝后跳转的地址
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest request,
                                       HttpServletResponse response,
                                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
                        request.setAttribute("message","抱歉你访问不了这个资源 ！——————😠");
                        request.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(request,response);
                    }
                })//访问被拒绝后执行

                .and()
                /*开启记住我的功能 ,此功能对应游览器上面有一个记住我（springSecurity 默认使用“remember-me”作为请求参数名字），
                我们也可以自己去设置，通过.rememberMeParameter()方法定制
                */
                .rememberMe()
                .tokenRepository(tokenRepository) //不加这个就表示去游览器上面存cookice，服务器Tomcat关闭就不能使用了表示我们采用数据库去存储我们在游览器上面记住我的内容





        ;
    }
    @Override
    //指定正确的账号密码,在此进行校验核对
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
          /*
            之前测试的假数据
          builder.inMemoryAuthentication() //在内存中完成账号密码的检查
                    .withUser("tom") //指定账号
                    /*.以前的".password("123")" 变成了 "password(new BCryptPasswordEncoder().encode("123"))"
                    这相当于对内存中的密码进行Bcrypt编码加密
                    如果比对时一致,说明密码正确,才允许登陆.
                     .passwordEncoder(new BCryptPasswordEncoder())
                    .password(new BCryptPasswordEncoder().encode("123"))//指定密码
                    .roles("ADMIN","学徒") //指定角色
                    .and()
                    .withUser("jerry") //指定账号
                    .password(new BCryptPasswordEncoder().encode("456"))
                    .authorities("UPDATE","内门弟子")//指定角色
                    ; */

          // 装配 UserDetailsService对象去数据库中查出对应的值
          builder.userDetailsService(myUserDetailsService)//注入userDetailsService的实现类
                  .passwordEncoder(getBCryptPasswordEncoder());//指定我们加密的格式


    }
}
